#include <iostream>
#include "string.h"

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include "libavutil/time.h"
#include <libavutil/log.h>
}
using namespace std;
//
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#define USE_H264BSF 0

void printErrWithCode(int errCode)
{
    char buf[1024] = {0};
    av_strerror(errCode, buf, sizeof(buf));
    cout << buf << endl;
    return;
}

int main()
{
    AVFormatContext *ictx = NULL;
    AVFormatContext *octx = NULL;
    string in_path = "/home/wzp/in.flv";
    string out_path = "rtmp://127.0.0.1:1935/live/room";
    int ret = 0;
    avformat_network_init();

    //打开文件，解封装协议头
    //类似于mp4都有文件头，文件头里放了所有的文件信息。
    ret = avformat_open_input(&ictx, in_path.c_str(), 0, 0);
    if (ret)
    {
        printErrWithCode(ret);
    }
    ret = avformat_find_stream_info(ictx, NULL);
    av_dump_format(ictx, 0, in_path.c_str(), false);

    //第二个参数是输出格式
    ret = avformat_alloc_output_context2(&octx, NULL, "flv", out_path.c_str());

    if (!octx)
    {
        printErrWithCode(ret);
    }

    //遍历所有输出流
    for (int i = 0; i < ictx->nb_streams; i++)
    {
        //创建一个输出流
        AVStream *out = avformat_new_stream(octx, ictx->streams[i]->codec->codec);
        if (!out)
        {
            ret = 0;
            printErrWithCode(ret);
        }

        //将输入流的配置信息（AVCodecContext）复制到输出流中
        //上面这种是老的方式，下面这种是新的，但是对mp4文件可能会有问题
        //        avcodec_copy_context(out->codec, ictx->streams[i]->codec);
        avcodec_parameters_copy(out->codecpar, ictx->streams[i]->codecpar);
        out->codec->codec_tag = 0;
    }
    av_dump_format(octx, 0, out_path.c_str(), true);

    //打开流，写入头信息，至此，输出上下文建立完成
    ret = avio_open(&octx->pb, out_path.c_str(), AVIO_FLAG_WRITE);
    if (!octx->pb)
    {
        printErrWithCode(ret);
    }
    ret = avformat_write_header(octx, 0);
    if (ret < 0)
    {
        printErrWithCode(ret);
    }

    //推流每一帧数据
    AVPacket pkt;
    long long startTime = av_gettime(); //获取当前的时间戳（微秒）
    while (1)
    {
        ret = av_read_frame(ictx, &pkt); //这一步虽然名字叫 read_frame，但实际上读取出来的是 packet
        if (ret)
        {
            printErrWithCode(ret);
        }
        //计算转换 pts dts（time base可能不同）（但是实际上，段点调试发现time_base是相同的，不知道是哪一步复制的time_base）
        //        AVRational itime = ictx->streams[pkt.stream_index]->time_base;
        //        AVRational otime = octx->streams[pkt.stream_index]->time_base;
        //        pkt.pts = av_rescale_q_rnd(pkt.pts, itime, otime,
        //                    (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        //        pkt.dts = av_rescale_q_rnd(pkt.dts, itime,otime,
        //                    (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        //        pkt.duration = av_rescale_q_rnd(pkt.duration, itime, otime,
        //                    (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        //        pkt.pos = -1;

        //这里假设网络传输是没问题的，那么我们应该在dts也就是解码时间进行推流。
        //如果推流时间早于解码时间，那么就等一会儿。
        if (ictx->streams[pkt.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            AVRational tb = ictx->streams[pkt.stream_index]->time_base;
            long long now = av_gettime() - startTime;
            long long dts = pkt.dts * (1000 * 1000 * av_q2d(tb)); //将秒转化为微秒
            if (dts > now)
            {
                av_usleep(dts - now);
            }
        }
        //根据pts dts进行排序，然后写入（由于是网络流，这一段就是网络传输）
        ret = av_interleaved_write_frame(octx, &pkt);
        if (ret < 0)
        {
            printErrWithCode(ret);
        }
        av_packet_unref(&pkt);
    }
}
